"""
配置管理模块
支持从YAML文件、环境变量等多种方式加载配置
"""

import os
import yaml
import json
from pathlib import Path
from typing import Dict, Any, Optional, Union
from dataclasses import dataclass, asdict
import logging

logger = logging.getLogger(__name__)


@dataclass
class ModelConfig:
    """模型相关配置"""
    model_name: str = "bert-base-chinese"
    num_labels: int = 8
    max_length: int = 128
    dropout_rate: float = 0.1
    hidden_size: Optional[int] = None


@dataclass
class TrainingConfig:
    """训练相关配置"""
    learning_rate: float = 2e-5
    batch_size: int = 16
    num_epochs: int = 3
    warmup_steps: int = 100
    weight_decay: float = 0.01
    eval_steps: int = 100
    save_steps: int = 500
    logging_steps: int = 50
    save_total_limit: int = 2
    load_best_model_at_end: bool = True
    metric_for_best_model: str = "accuracy"
    fp16: bool = False
    seed: int = 42
    output_dir: str = "models/checkpoints"


@dataclass
class DataConfig:
    """数据相关配置"""
    data_path: str = "data/raw/intent_data.csv"
    processed_data_dir: str = "data/processed"
    test_size: float = 0.3
    val_size: float = 0.5
    random_state: int = 42
    min_text_length: int = 1
    max_text_length: int = 512


@dataclass
class APIConfig:
    """API服务配置"""
    host: str = "0.0.0.0"
    port: int = 5000
    debug: bool = False
    model_path: str = "./final-bert-intent-model"
    confidence_threshold: float = 0.5
    max_text_length: int = 512
    max_batch_size: int = 100
    enable_cors: bool = True


@dataclass
class LoggingConfig:
    """日志配置"""
    level: str = "INFO"
    log_dir: str = "logs"
    enable_file_logging: bool = True
    max_file_size: int = 10 * 1024 * 1024  # 10MB
    backup_count: int = 5
    console_output: bool = True


@dataclass
class PathsConfig:
    """路径配置"""
    project_root: str = "."
    data_dir: str = "data"
    models_dir: str = "models"
    configs_dir: str = "configs"
    logs_dir: str = "logs"
    scripts_dir: str = "scripts"


@dataclass
class ProjectConfig:
    """项目总配置"""
    model: ModelConfig
    training: TrainingConfig
    data: DataConfig
    api: APIConfig
    logging: LoggingConfig
    paths: PathsConfig
    
    def to_dict(self) -> Dict[str, Any]:
        """转换为字典"""
        return asdict(self)
    
    def save_to_file(self, file_path: str):
        """保存配置到文件"""
        Path(file_path).parent.mkdir(parents=True, exist_ok=True)
        
        config_dict = self.to_dict()
        
        if file_path.endswith('.yaml') or file_path.endswith('.yml'):
            with open(file_path, 'w', encoding='utf-8') as f:
                yaml.dump(config_dict, f, default_flow_style=False, allow_unicode=True)
        elif file_path.endswith('.json'):
            with open(file_path, 'w', encoding='utf-8') as f:
                json.dump(config_dict, f, ensure_ascii=False, indent=2)
        else:
            raise ValueError(f"不支持的配置文件格式: {file_path}")
        
        logger.info(f"配置已保存到: {file_path}")


class ConfigManager:
    """配置管理器"""
    
    def __init__(self):
        """初始化配置管理器"""
        self.config = None
        self._loaded_from = None
    
    def load_from_file(self, file_path: str) -> ProjectConfig:
        """
        从文件加载配置
        
        Args:
            file_path: 配置文件路径
            
        Returns:
            ProjectConfig: 配置对象
        """
        if not Path(file_path).exists():
            raise FileNotFoundError(f"配置文件不存在: {file_path}")
        
        logger.info(f"从文件加载配置: {file_path}")
        
        try:
            if file_path.endswith('.yaml') or file_path.endswith('.yml'):
                with open(file_path, 'r', encoding='utf-8') as f:
                    config_dict = yaml.safe_load(f)
            elif file_path.endswith('.json'):
                with open(file_path, 'r', encoding='utf-8') as f:
                    config_dict = json.load(f)
            else:
                raise ValueError(f"不支持的配置文件格式: {file_path}")
            
            self.config = self._dict_to_config(config_dict)
            self._loaded_from = file_path
            
            logger.info("配置加载成功")
            return self.config
            
        except Exception as e:
            logger.error(f"加载配置文件失败: {e}")
            raise
    
    def load_from_env(self) -> ProjectConfig:
        """
        从环境变量加载配置
        
        Returns:
            ProjectConfig: 配置对象
        """
        logger.info("从环境变量加载配置")
        
        # 模型配置
        model_config = ModelConfig(
            model_name=os.getenv('MODEL_NAME', 'bert-base-chinese'),
            num_labels=int(os.getenv('NUM_LABELS', '8')),
            max_length=int(os.getenv('MAX_LENGTH', '128')),
            dropout_rate=float(os.getenv('DROPOUT_RATE', '0.1'))
        )
        
        # 训练配置
        training_config = TrainingConfig(
            learning_rate=float(os.getenv('LEARNING_RATE', '2e-5')),
            batch_size=int(os.getenv('BATCH_SIZE', '16')),
            num_epochs=int(os.getenv('NUM_EPOCHS', '3')),
            warmup_steps=int(os.getenv('WARMUP_STEPS', '100')),
            weight_decay=float(os.getenv('WEIGHT_DECAY', '0.01')),
            seed=int(os.getenv('SEED', '42'))
        )
        
        # 数据配置
        data_config = DataConfig(
            data_path=os.getenv('DATA_PATH', 'data/raw/intent_data.csv'),
            test_size=float(os.getenv('TEST_SIZE', '0.3')),
            val_size=float(os.getenv('VAL_SIZE', '0.5')),
            random_state=int(os.getenv('RANDOM_STATE', '42'))
        )
        
        # API配置
        api_config = APIConfig(
            host=os.getenv('HOST', '0.0.0.0'),
            port=int(os.getenv('PORT', '5000')),
            debug=os.getenv('DEBUG', 'False').lower() == 'true',
            model_path=os.getenv('MODEL_PATH', './final-bert-intent-model'),
            confidence_threshold=float(os.getenv('CONFIDENCE_THRESHOLD', '0.5'))
        )
        
        # 日志配置
        logging_config = LoggingConfig(
            level=os.getenv('LOG_LEVEL', 'INFO'),
            log_dir=os.getenv('LOG_DIR', 'logs'),
            enable_file_logging=os.getenv('ENABLE_FILE_LOGGING', 'true').lower() == 'true'
        )
        
        # 路径配置
        paths_config = PathsConfig(
            project_root=os.getenv('PROJECT_ROOT', '.'),
            data_dir=os.getenv('DATA_DIR', 'data'),
            models_dir=os.getenv('MODELS_DIR', 'models'),
            configs_dir=os.getenv('CONFIGS_DIR', 'configs'),
            logs_dir=os.getenv('LOGS_DIR', 'logs')
        )
        
        self.config = ProjectConfig(
            model=model_config,
            training=training_config,
            data=data_config,
            api=api_config,
            logging=logging_config,
            paths=paths_config
        )
        
        self._loaded_from = "environment_variables"
        
        logger.info("从环境变量加载配置完成")
        return self.config
    
    def load_default(self) -> ProjectConfig:
        """
        加载默认配置
        
        Returns:
            ProjectConfig: 默认配置对象
        """
        logger.info("加载默认配置")
        
        self.config = ProjectConfig(
            model=ModelConfig(),
            training=TrainingConfig(),
            data=DataConfig(),
            api=APIConfig(),
            logging=LoggingConfig(),
            paths=PathsConfig()
        )
        
        self._loaded_from = "default"
        
        logger.info("默认配置加载完成")
        return self.config
    
    def load_config(self, config_file: Optional[str] = None, 
                   use_env: bool = True, 
                   use_default: bool = True) -> ProjectConfig:
        """
        智能加载配置
        
        Args:
            config_file: 配置文件路径
            use_env: 是否使用环境变量
            use_default: 是否使用默认配置
            
        Returns:
            ProjectConfig: 配置对象
        """
        # 优先级：配置文件 > 环境变量 > 默认配置
        
        if config_file and Path(config_file).exists():
            return self.load_from_file(config_file)
        
        if use_env:
            try:
                return self.load_from_env()
            except Exception as e:
                logger.warning(f"从环境变量加载配置失败: {e}")
        
        if use_default:
            return self.load_default()
        
        raise RuntimeError("无法加载任何配置")
    
    def _dict_to_config(self, config_dict: Dict[str, Any]) -> ProjectConfig:
        """
        将字典转换为配置对象
        
        Args:
            config_dict: 配置字典
            
        Returns:
            ProjectConfig: 配置对象
        """
        # 提取各部分配置
        model_dict = config_dict.get('model', {})
        training_dict = config_dict.get('training', {})
        data_dict = config_dict.get('data', {})
        api_dict = config_dict.get('api', {})
        logging_dict = config_dict.get('logging', {})
        paths_dict = config_dict.get('paths', {})
        
        # 创建配置对象
        model_config = ModelConfig(**model_dict)
        training_config = TrainingConfig(**training_dict)
        data_config = DataConfig(**data_dict)
        api_config = APIConfig(**api_dict)
        logging_config = LoggingConfig(**logging_dict)
        paths_config = PathsConfig(**paths_dict)
        
        return ProjectConfig(
            model=model_config,
            training=training_config,
            data=data_config,
            api=api_config,
            logging=logging_config,
            paths=paths_config
        )
    
    def get_config(self) -> Optional[ProjectConfig]:
        """获取当前配置"""
        return self.config
    
    def update_config(self, **kwargs):
        """
        更新配置
        
        Args:
            **kwargs: 要更新的配置项
        """
        if self.config is None:
            logger.warning("配置尚未加载")
            return
        
        # 更新配置
        for section, updates in kwargs.items():
            if hasattr(self.config, section):
                section_config = getattr(self.config, section)
                for key, value in updates.items():
                    if hasattr(section_config, key):
                        setattr(section_config, key, value)
                        logger.info(f"更新配置: {section}.{key} = {value}")
                    else:
                        logger.warning(f"未知配置项: {section}.{key}")
            else:
                logger.warning(f"未知配置段: {section}")
    
    def get_info(self) -> Dict[str, Any]:
        """获取配置管理器信息"""
        return {
            'loaded_from': self._loaded_from,
            'config_loaded': self.config is not None,
            'config_sections': list(asdict(self.config).keys()) if self.config else []
        }


# 全局配置管理器实例
config_manager = ConfigManager()


def get_config() -> Optional[ProjectConfig]:
    """获取全局配置"""
    return config_manager.get_config()


def load_config(config_file: Optional[str] = None) -> ProjectConfig:
    """加载全局配置"""
    return config_manager.load_config(config_file)


def create_default_configs():
    """创建默认配置文件"""
    config = ProjectConfig(
        model=ModelConfig(),
        training=TrainingConfig(),
        data=DataConfig(),
        api=APIConfig(),
        logging=LoggingConfig(),
        paths=PathsConfig()
    )
    
    # 创建配置目录
    configs_dir = Path("configs")
    configs_dir.mkdir(exist_ok=True)
    
    # 保存各种格式的配置文件
    config.save_to_file("configs/default_config.yaml")
    config.save_to_file("configs/default_config.json")
    
    logger.info("默认配置文件已创建")


if __name__ == "__main__":
    # 测试配置管理
    
    # 创建默认配置文件
    create_default_configs()
    
    # 测试配置管理器
    manager = ConfigManager()
    
    # 加载默认配置
    config = manager.load_default()
    print("默认配置:")
    print(f"模型名称: {config.model.model_name}")
    print(f"批次大小: {config.training.batch_size}")
    print(f"API端口: {config.api.port}")
    
    # 更新配置
    manager.update_config(
        training={'batch_size': 32, 'num_epochs': 5},
        api={'port': 8080}
    )
    
    updated_config = manager.get_config()
    print(f"\n更新后配置:")
    print(f"批次大小: {updated_config.training.batch_size}")
    print(f"API端口: {updated_config.api.port}")
    
    # 获取管理器信息
    info = manager.get_info()
    print(f"\n配置管理器信息: {info}")
    
    print("\n配置模块测试完成!") 